package pl;

import javacard.framework.Util;
import javacard.security.MessageDigest;

public class HMAC {

	/*
	 * Refer to RFC2104.
	 * 
	 * H(K XOR opad, H(K XOR ipad, text))
	 * 
	 * where K is an n byte key ipad is the byte 0x36 repeated 64 times opad is
	 * the byte 0x5c repeated 64 times and text is the data being protected
	 */
	/*
	 * Test vectors byte[] key = new byte[]{(byte)0x0b, (byte)0x0b, (byte)0x0b,
	 * (byte)0x0b,(byte)0x0b, (byte)0x0b, (byte)0x0b, (byte)0x0b, (byte)0x0b,
	 * (byte)0x0b, (byte)0x0b, (byte)0x0b,(byte)0x0b, (byte)0x0b, (byte)0x0b,
	 * (byte)0x0b, (byte)0x0b, (byte)0x0b, (byte)0x0b, (byte)0x0b};
	 * 
	 * byte[] data = new byte[] {(byte)0x48, (byte)0x69, (byte)0x20, (byte)0x54,
	 * (byte)0x68, (byte)0x65, (byte)0x72, (byte)0x65};
	 * 
	 * result for sha-256
	 * b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7
	 */

	public static final byte ALG_HMAC_SHA1 = 24;
	public static final byte ALG_HMAC_SHA_256 = 25;
	public static final byte ALG_HMAC_SHA_384 = 26;
	public static final byte ALG_HMAC_SHA_512 = 27;
	private final short BLOCKSIZE;
	
	short size;
	
	byte[] tmp;
	byte[] k_xor_ipad;
	byte[] k_xor_opad;
	byte[] key;
	
	private MessageDigest md;

	public HMAC(MessageDigest mdInstance) {

		switch (mdInstance.getAlgorithm()) {

		case MessageDigest.ALG_SHA:
			md = MessageDigest.getInstance(MessageDigest.ALG_SHA, false); 
			break;
		case MessageDigest.ALG_SHA_256:
			md = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); 
			break;
		case MessageDigest.ALG_SHA_384:
			md = MessageDigest.getInstance(MessageDigest.ALG_SHA_384, false); 
			break;
		case MessageDigest.ALG_SHA_512:
			md = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false); 
			break;
		default:
			md = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); 
			break;
		}
		
		this.size = md.getLength();
		tmp = new byte[md.getLength()];
		BLOCKSIZE = 64;

		k_xor_ipad = new byte[BLOCKSIZE];
		k_xor_opad = new byte[BLOCKSIZE];
	}
	
	public void init(byte[] key)
	{
		if (key.length > BLOCKSIZE) {
			md.reset();
			md.doFinal(key, (short) 0, (short) key.length, tmp, (short) 0);
			key = tmp;
		}

		Util.arrayCopy(key, (short) 0, k_xor_ipad, (short) 0,
				(short) key.length);
		Util.arrayCopy(key, (short) 0, k_xor_opad, (short) 0,
				(short) key.length);

		for (short i = 0; i < BLOCKSIZE; i++) {
			k_xor_ipad[i] ^= 0x36;
			k_xor_opad[i] ^= 0x5C;
		}

		md.update(k_xor_ipad, (short) 0, BLOCKSIZE);
	}
	
	public short getDigestLength() {
		return size;
	}

	public void update(byte[] b) {
		md.update(b, (short) 0, (short) b.length);
	}

	public void reset() {
		md.reset();
		md.update(k_xor_ipad, (short) 0, (short) k_xor_ipad.length);
	}

	public void digest(byte[] out) {
		digest(out, (short) 0);
	}

	public void digest(byte[] out, short off) {
		md.doFinal(new byte[0], (short) 0, (short) 0, tmp, (short) 0);
		md.update(k_xor_opad, (short) 0, (short) k_xor_opad.length);
		md.doFinal(tmp, (short) 0, (short) tmp.length, out, (short) 0);
		md.update(k_xor_ipad, (short) 0, (short) k_xor_ipad.length);
	}
}